package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
)

const (
	// AppName 表示应用程序名称。
	AppName string = "fury"
	// AppVersion 表示应用程序版本。
	AppVersion string = "0.1.0"
	// DefaultConfigFile 表示配置文件名。
	DefaultConfigFile string = AppName + ".json"
	// DefaultDebug 表示默认是否开启Debug模式。
	DefaultDebug bool = false
)

var (
	// flagVersion 是否显示版权信息
	flagVersion bool
	// flagHelp 是否显示帮助信息
	flagHelp bool
	// flagVerify 是否只检查配置文件
	flagVerify bool

	config *configuration = &configuration{}

	whitespaceSplitter, _ = regexp.Compile(`\s+`)
)

func init() {
	flag.BoolVar(&flagVersion, "version", false, "Shows version message")
	flag.BoolVar(&flagHelp, "h", false, "Shows this help message")
	flag.BoolVar(&flagVerify, "verify", false, "Verify configuration and quit")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s -version\n", AppName)
		fmt.Fprintf(os.Stderr, "Usage: %s -h\n", AppName)
		fmt.Fprintf(os.Stderr, "Usage: %s -verify\n", AppName)
		fmt.Fprintf(os.Stderr, "Usage: %s [CONFIG_FILE]\n", AppName)
		flag.PrintDefaults()
	}
}

func main() {
	flag.Parse()

	if flagVersion {
		fmt.Printf("%s version %s\n", AppName, AppVersion)
		return
	}

	if flagHelp {
		flag.Usage()
		return
	}

	// 加载配置。
	if err := loadConfig(strings.TrimSpace(flag.Arg(0))); err != nil {
		fmt.Fprintf(os.Stderr, "Cannot load configuration\n")
		panic(err)
	}

	if flagVerify {
		fmt.Printf("Configuration:\n%s\n", config)
		return
	}

	setUp()

	start([]string{})

	err := runForEver()
	if err != nil {
		panic(err)
	}
}

func runForEver() error {
	go processCmdCycle()

	sigChannel := make(chan os.Signal, 256)
	signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	for {
		sig := <-sigChannel
		fmt.Fprintf(os.Stderr, "Received sig: %#v\n", sig)
		switch sig {
		case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM:
			stop([]string{})
			return nil
		}
	}
}

func processCmdCycle() {
	stdin := bufio.NewReader(os.Stdin)
	for {
		// 打印提示符
		fmt.Print(": ")
		textLine, err := stdin.ReadString('\n')
		if err != nil {
			continue
		}

		textLine = strings.ToLower(strings.TrimSpace(textLine))
		if len(textLine) == 0 {
			continue
		}

		var cmd string
		var serviceNames []string
		cmdLine := whitespaceSplitter.Split(textLine, 100)
		if len(cmdLine) == 0 {
			continue
		} else {
			cmd = cmdLine[0]
			serviceNames = cmdLine[1:]
		}

		if cmd == "exit" {
			stop([]string{})
			fmt.Println("OK")
			os.Exit(0)
		} else if cmd == "help" {
			fmt.Println("restart [service name]\tRestart service")
			fmt.Println("ps [service name]     \tShow process info of service")
			fmt.Println("logs [service name]   \tShow stdout & stderr of service")
			fmt.Println("env [service name]    \tShow env of service")
		} else if cmd == "ps" {
			showPs(serviceNames)
		} else if cmd == "logs" {
			showLogs(serviceNames)
		} else if cmd == "env" {
			showEnv(serviceNames)
		} else if cmd == "restart" {
			stop(serviceNames)
			start(serviceNames)
		} else {
			fmt.Printf("Unknown command: %s\n", cmd)
		}
	}
}

// 加载配置文件并校验格式。
func loadConfig(configFile string) (err error) {
	if configFile == "" {
		configFile = DefaultConfigFile
	}

	configFile, err = filepath.Abs(configFile)
	if err != nil {
		return err
	}

	configFileStat, err := os.Stat(configFile)
	if err != nil {
		return err
	}

	if configFileStat.IsDir() {
		configFile = filepath.Join(configFile, DefaultConfigFile)
	}

	err = loadConfigFromFile(configFile)
	if err != nil {
		return err
	}

	// 配置文件所在的目录作为默认工作目录。
	defaultWorkDir := filepath.Dir(configFile)

	// 检查环境变量的名字不能为空。
	for envName := range config.Env {
		s := strings.TrimSpace(envName)
		if s == "" {
			return fmt.Errorf("illegal blank environment variable name")
		}
	}

	if len(config.Services) == 0 {
		return fmt.Errorf("illegal empty services")
	}

	// 检查
	// 1. 服务名不能为空。
	// 2. 服务的`Cmd`字段不能为空。
	for serviceName, serviceConfig := range config.Services {
		s := strings.TrimSpace(serviceName)
		if s == "" {
			return fmt.Errorf("illegal blank service name")
		}

		s = strings.TrimSpace(serviceConfig.Cmd)
		if s == "" {
			return fmt.Errorf("illegal blank cmd of service %v", serviceName)
		}

		workDir := strings.TrimSpace(serviceConfig.WorkDir)
		if workDir == "" {
			serviceConfig.WorkDir = defaultWorkDir
		} else {
			serviceConfig.WorkDir = workDir
		}
	}

	return nil
}

// 从指定的文件中加载配置。
func loadConfigFromFile(configFile string) (err error) {
	var cf *os.File

	fmt.Printf("Load configuration from %s\n", configFile)

	if cf, err = os.Open(configFile); err != nil {
		return err
	}

	defer cf.Close()

	dec := json.NewDecoder(cf)
	return dec.Decode(&config)
}
